package com.pig.sentinel.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 性能统计
 *
 * @author lizhiwei
 */
@Aspect
@Component
public class PerformanceStatisticsAop {
    public static final Logger logger = LoggerFactory.getLogger(PerformanceStatisticsAop.class);
    private Map<String, Statistics> performanceMap = new ConcurrentHashMap<>();

    @Pointcut("@annotation(com.pig.sentinel.annotions.Sentinel)")
    public void service() {
    }

    @Before("service()")
    public void before(JoinPoint joinPoint) {
        String simpleClassName = joinPoint.getSignature().getDeclaringType().getName();
        String methodName = joinPoint.getSignature().getName();
        //logger.info("before:{}.{}.params:{}", simpleClassName, methodName, Arrays.asList(joinPoint.getArgs()));
    }

    /**
     * service性能
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("service()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            String simpleClassName = proceedingJoinPoint.getSignature().getDeclaringType().getSimpleName();
            String methodName = proceedingJoinPoint.getSignature().getName();
            long start = System.currentTimeMillis();
            Object obj = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();
            addProcessExeTime(simpleClassName + "." + methodName, end - start);
            return obj;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 后置异常通知
     * 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
     * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     *
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "service()", throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
        String simpleClassName = joinPoint.getSignature().getDeclaringType().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        logger.error(simpleClassName + "." + methodName, exception);
    }

    /**
     * 性能分析
     *
     * @param name
     * @param consumeTime
     */
    private void addProcessExeTime(String name, long consumeTime) {
        Statistics statistics = performanceMap.get(name);
        if (statistics == null) {
            statistics = new Statistics();
            statistics.consumeSumTime.addAndGet(consumeTime);
            performanceMap.put(name, statistics);
        } else {
            statistics.callTimes.incrementAndGet();
            statistics.consumeSumTime.addAndGet(consumeTime);
        }
        //logger.info("exeTime:{}", statistics);
    }

    /**
     * 打印出性能
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void printProcessExeTime() {
        logger.info("printServiceExeTime");
        for (Map.Entry<String, Statistics> entry : performanceMap.entrySet()) {
            Statistics value = entry.getValue();
            String key = entry.getKey();
            logger.info("{},times={},sum={},avg={}", key, value.callTimes, value.consumeSumTime,
                    value.consumeSumTime.longValue() / value.callTimes.longValue());
        }
    }

    private class Statistics {
        AtomicLong callTimes = new AtomicLong(0);
        AtomicLong consumeSumTime = new AtomicLong(0);

        @Override
        public String toString() {
            return "Statistics{" +
                    "callTimes=" + callTimes +
                    ", consumeSumTime=" + consumeSumTime +
                    '}';
        }
    }
}  